package com.drivingbarefoot.games.OneFour24;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;

import org.anddev.andengine.audio.sound.Sound;
import org.anddev.andengine.audio.sound.SoundFactory;
import org.anddev.andengine.engine.Engine;
import org.anddev.andengine.engine.camera.Camera;
import org.anddev.andengine.engine.options.EngineOptions;
import org.anddev.andengine.engine.options.EngineOptions.ScreenOrientation;
import org.anddev.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;
import org.anddev.andengine.entity.primitive.Rectangle;
import org.anddev.andengine.entity.scene.Scene;
import org.anddev.andengine.entity.scene.Scene.IOnAreaTouchListener;
import org.anddev.andengine.entity.scene.Scene.IOnSceneTouchListener;
import org.anddev.andengine.entity.scene.Scene.ITouchArea;
import org.anddev.andengine.entity.scene.background.ColorBackground;
import org.anddev.andengine.entity.shape.Shape;
import org.anddev.andengine.entity.sprite.AnimatedSprite;
import org.anddev.andengine.entity.sprite.Sprite;
import org.anddev.andengine.entity.text.ChangeableText;
import org.anddev.andengine.entity.util.FPSLogger;
import org.anddev.andengine.extension.physics.box2d.PhysicsConnector;
import org.anddev.andengine.extension.physics.box2d.PhysicsFactory;
import org.anddev.andengine.extension.physics.box2d.PhysicsWorld;
import org.anddev.andengine.input.touch.TouchEvent;
import org.anddev.andengine.opengl.font.Font;
import org.anddev.andengine.opengl.texture.Texture;
import org.anddev.andengine.opengl.texture.TextureOptions;
import org.anddev.andengine.opengl.texture.region.TextureRegion;
import org.anddev.andengine.opengl.texture.region.TextureRegionFactory;
import org.anddev.andengine.opengl.texture.region.TiledTextureRegion;
import org.anddev.andengine.sensor.accelerometer.AccelerometerData;
import org.anddev.andengine.sensor.accelerometer.IAccelerometerListener;
import org.anddev.andengine.ui.activity.BaseGameActivity;
import org.anddev.andengine.util.Debug;
import org.anddev.andengine.util.MathUtils;

import android.graphics.Color;
import android.graphics.Typeface;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Toast;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.FixtureDef;

public class GameActivity extends BaseGameActivity implements
		IAccelerometerListener, IOnSceneTouchListener, IOnAreaTouchListener,
		ContactListener {
	// ===========================================================
	// Constants
	// ===========================================================

	public static final int[][] DIE_INDEX = { { 0, 6, 1, 2, 3, 4, 5 },
			{ 0, 1, 3, 4, 5, 0, 2 }, { 0, 2, 3, 6, 0, 4, 1 },
			{ 0, 3, 0, 2, 1, 5, 4 }, { 0, 4, 5, 0, 3, 2, 1 },
			{ 0, 5, 0, 2, 4, 1, 3 } };

	private static final int CAMERA_WIDTH = 300;
	private static final int CAMERA_HEIGHT = 450;

	// ===========================================================
	// Fields
	// ===========================================================

	// game fields
	private GAME_STATE current_state = GAME_STATE.NOTROLLING;
	private int mFaceCount = 0;

	private static long elapsedTime = 0;

	long lastShakeTime = 0;
	float lastShakeX, lastShakeY, lastShakeZ = 0;
	boolean shaking = false;

	private final HashMap<Integer, Die> dice = new HashMap<Integer, Die>(6);

	// graphics fields
	private Camera mCamera;
	private Scene mMainScene;
	private Scene mHelpScene;

	private Texture mRulesButton;
	private Texture mRulesBackgroundTexture;

	private Texture mHelpButton;

	private Sprite mRulesBackgroundSprite;
	private Sprite button;

	private Texture mDiceTexture;
	private final Vector2 mTempVector = new Vector2();

	private PhysicsWorld mPhysicsWorld;

	private TiledTextureRegion[] mDiceTextureRegion = new TiledTextureRegion[6];

	private TextureRegion mRulesButtonRegion;
	private TextureRegion mHelpButtonRegion;
	private TextureRegion mRulesBackgroundRegion;

	// sound fields
	private Sound mDiceSoundHard;
	private Sound mDiceSoundSoft;

	// text fields
	private Texture mFontTexture;

	private Font mFont;

	private ChangeableText mScoreText;

	@Override
	public Engine onLoadEngine() {
		Toast.makeText(
				this,
				"Shake phone to roll the dice.  Touch one or more dice to keep them and roll again.",
				Toast.LENGTH_LONG).show();
		mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
		return new Engine(
				new EngineOptions(true, ScreenOrientation.PORTRAIT,
						new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT),
						mCamera).setNeedsSound(true));
	}

	@Override
	public void onLoadResources() {
		// load texture resources
		this.mDiceTexture = new Texture(512, 512,
				TextureOptions.BILINEAR_PREMULTIPLYALPHA);
		this.mRulesButton = new Texture(64, 32,
				TextureOptions.BILINEAR_PREMULTIPLYALPHA);
		this.mRulesBackgroundTexture = new Texture(512, 512,
				TextureOptions.BILINEAR_PREMULTIPLYALPHA);
		this.mHelpButton = new Texture(64, 32,
				TextureOptions.BILINEAR_PREMULTIPLYALPHA);

		TextureRegionFactory.setAssetBasePath("gfx/");

		this.mDiceTextureRegion[0] = TextureRegionFactory.createTiledFromAsset(
				this.mDiceTexture, this, "dice1.png", 0, 0, 7, 1);
		this.mDiceTextureRegion[1] = TextureRegionFactory.createTiledFromAsset(
				this.mDiceTexture, this, "dice2.png", 0, 50, 7, 1);
		this.mDiceTextureRegion[2] = TextureRegionFactory.createTiledFromAsset(
				this.mDiceTexture, this, "dice3.png", 0, 100, 7, 1);
		this.mDiceTextureRegion[3] = TextureRegionFactory.createTiledFromAsset(
				this.mDiceTexture, this, "dice4.png", 0, 150, 7, 1);
		this.mDiceTextureRegion[4] = TextureRegionFactory.createTiledFromAsset(
				this.mDiceTexture, this, "dice5.png", 0, 200, 7, 1);
		this.mDiceTextureRegion[5] = TextureRegionFactory.createTiledFromAsset(
				this.mDiceTexture, this, "dice6.png", 0, 250, 7, 1);

		this.mRulesButtonRegion = TextureRegionFactory.createFromAsset(
				mRulesButton, this, "btn_rules.png", 0, 0);
		this.mHelpButtonRegion = TextureRegionFactory.createFromAsset(
				mHelpButton, this, "btn_help.png", 0, 0);

		this.mRulesBackgroundRegion = TextureRegionFactory.createFromAsset(
				mRulesBackgroundTexture, this, "bg_rules.png", 0, 0);

		this.mEngine.getTextureManager().loadTexture(this.mDiceTexture);
		this.mEngine.getTextureManager().loadTexture(this.mRulesButton);
		this.mEngine.getTextureManager().loadTexture(this.mHelpButton);
		this.mEngine.getTextureManager().loadTexture(
				this.mRulesBackgroundTexture);

		this.enableAccelerometerSensor(this);

		// load sound resources
		SoundFactory.setAssetBasePath("mfx/");
		try {
			this.mDiceSoundHard = SoundFactory.createSoundFromAsset(
					this.mEngine.getSoundManager(), this, "dice_roll_hard.ogg");
			this.mDiceSoundSoft = SoundFactory.createSoundFromAsset(
					this.mEngine.getSoundManager(), this, "dice_roll.ogg");
		} catch (final IOException e) {
			Debug.e("Error", e);
		}

		// load text/font resources
		this.mFontTexture = new Texture(256, 256,
				TextureOptions.BILINEAR_PREMULTIPLYALPHA);

		this.mFont = new Font(this.mFontTexture, Typeface.create(
				Typeface.DEFAULT, Typeface.NORMAL), 16, true, Color.WHITE);

		this.mEngine.getTextureManager().loadTexture(this.mFontTexture);
		this.mEngine.getFontManager().loadFont(this.mFont);
	}

	@Override
	public Scene onLoadScene() {
		this.mEngine.registerUpdateHandler(new FPSLogger());

		this.mPhysicsWorld = new PhysicsWorld(new Vector2(0, 0), true);

		this.mHelpScene = new Scene(2);

		mRulesBackgroundSprite = new Sprite(0, 0, mRulesBackgroundRegion);

		this.mHelpScene.getBottomLayer().addEntity(mRulesBackgroundSprite);

		this.mHelpScene.setOnSceneTouchListener(new IOnSceneTouchListener() {
			@Override
			public boolean onSceneTouchEvent(Scene pScene,
					TouchEvent pSceneTouchEvent) {
				if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_DOWN) {
					mMainScene.clearChildScene();
					return true;
				}
				return false;
			}
		});

		this.mHelpScene.setBackgroundEnabled(false);

		mMainScene = new Scene(1);
		mMainScene.setBackground(new ColorBackground(0, 0, 0));

		final Shape ground2 = new Rectangle(0, CAMERA_HEIGHT - 2, CAMERA_WIDTH,
				2);
		final Shape ground = new Rectangle(0, CAMERA_HEIGHT - 60, CAMERA_WIDTH,
				2);
		final Shape roof = new Rectangle(0, 40, CAMERA_WIDTH, 2);
		final Shape left = new Rectangle(0, 40, 2, CAMERA_HEIGHT - 2);
		final Shape right = new Rectangle(CAMERA_WIDTH - 2, 40, 2,
				CAMERA_HEIGHT - 2);

		final FixtureDef wallFixtureDef = PhysicsFactory.createFixtureDef(0,
				0.5f, 0.5f);
		PhysicsFactory.createBoxBody(this.mPhysicsWorld, ground,
				BodyType.StaticBody, wallFixtureDef);
		PhysicsFactory.createBoxBody(this.mPhysicsWorld, ground2,
				BodyType.StaticBody, wallFixtureDef);
		PhysicsFactory.createBoxBody(this.mPhysicsWorld, roof,
				BodyType.StaticBody, wallFixtureDef);
		PhysicsFactory.createBoxBody(this.mPhysicsWorld, left,
				BodyType.StaticBody, wallFixtureDef);
		PhysicsFactory.createBoxBody(this.mPhysicsWorld, right,
				BodyType.StaticBody, wallFixtureDef);

		mMainScene.getBottomLayer().addEntity(ground);
		mMainScene.getBottomLayer().addEntity(ground2);
		mMainScene.getBottomLayer().addEntity(roof);
		mMainScene.getBottomLayer().addEntity(left);
		mMainScene.getBottomLayer().addEntity(right);

		mMainScene.registerUpdateHandler(this.mPhysicsWorld);

		mMainScene.setOnSceneTouchListener(this);

		mMainScene.setOnAreaTouchListener(this);

		mPhysicsWorld.setContactListener(this);

		mScoreText = new ChangeableText(2.0f, 10.0f, mFont, GetScoreText(0), 20);

		mMainScene.getTopLayer().addEntity(mScoreText);

		return mMainScene;
	}

	private int diceSelectedThisTurn = 0;

	@Override
	public boolean onAreaTouched(final TouchEvent pSceneTouchEvent,
			final ITouchArea pTouchArea, final float pTouchAreaLocalX,
			final float pTouchAreaLocalY) {
		if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_DOWN) {
			this.mPhysicsWorld.postRunnable(new Runnable() {
				@Override
				public void run() {
					if (current_state != GAME_STATE.ROLLED
							&& current_state != GAME_STATE.CANROLL) // ignore
						// touches
						// if we
						// haven't
						// just
						// rolled
						return;

					SetGameState(GAME_STATE.EVALUATING_ROLL);

					final AnimatedSprite face = (AnimatedSprite) pTouchArea;

					Die d = dice.get(face.hashCode());
					if (d.isInPlay()) {
						d.ToggleKept();

						if (d.isKept())
							diceSelectedThisTurn++;
						else
							diceSelectedThisTurn--;
					}

					Rules rules = new Rules(dice);
					if (rules.IsGameOver()) {
						String result;
						if (!rules.DidUserQualify()) {
							result = "Sorry, you did not qualify.  Touch the screen to restart.";
							mScoreText.setText(GetScoreText(0));
						} else {
							int score = rules.GetScore();
							result = String
									.format("You scored %d. Touch the screen to restart.",
											score);
							mScoreText.setText(GetScoreText(score));
						}

						DisplayToast(result);

						SetGameState(GAME_STATE.GAMEOVER);
					} else if (diceSelectedThisTurn >= 1)
						SetGameState(GAME_STATE.CANROLL);
					else
						SetGameState(GAME_STATE.ROLLED);
				}
			});
			return true;
		}

		return false;
	}

	public void onLoadComplete() {
		GameActivity.this.addFace(MathUtils.random(25, CAMERA_WIDTH - 50),
				MathUtils.random(75, CAMERA_HEIGHT - 105));
		GameActivity.this.addFace(MathUtils.random(25, CAMERA_WIDTH - 50),
				MathUtils.random(75, CAMERA_HEIGHT - 105));
		GameActivity.this.addFace(MathUtils.random(25, CAMERA_WIDTH - 50),
				MathUtils.random(75, CAMERA_HEIGHT - 105));
		GameActivity.this.addFace(MathUtils.random(25, CAMERA_WIDTH - 50),
				MathUtils.random(75, CAMERA_HEIGHT - 105));
		GameActivity.this.addFace(MathUtils.random(25, CAMERA_WIDTH - 50),
				MathUtils.random(75, CAMERA_HEIGHT - 105));
		GameActivity.this.addFace(MathUtils.random(25, CAMERA_WIDTH - 50),
				MathUtils.random(75, CAMERA_HEIGHT - 105));

		addRulesButton(CAMERA_WIDTH - 2.0f - 64.0f, 5.0f);
		addHelpButton(CAMERA_WIDTH - 2.0f - 64.0f - 4.0f - 64.0f, 5.0f);

		SetGameState(GAME_STATE.NOTROLLING);
	}

	@Override
	public boolean onSceneTouchEvent(final Scene pScene,
			final TouchEvent pSceneTouchEvent) {
		if (this.mPhysicsWorld != null) {
			if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_DOWN) {
				this.runOnUpdateThread(new Runnable() {
					@Override
					public void run() {
						if (current_state == GAME_STATE.GAMEOVER)
							RestartGame(); // restart game
					}
				});
				return true;
			}
		}
		return false;
	}

	private void RestartGame() {
		Iterator<Die> i = dice.values().iterator();

		while (i.hasNext()) {
			Die d = i.next();
			d.Reset();
		}

		diceSelectedThisTurn = 0;
		SetGameState(GAME_STATE.NOTROLLING);
	}

	@Override
	public void onAccelerometerChanged(
			final AccelerometerData pAccelerometerData) {
		if (current_state != GAME_STATE.CANROLL
				&& current_state != GAME_STATE.NOTROLLING) // disregard s
			return;

		long currentTime = System.currentTimeMillis();

		if ((currentTime - lastShakeTime) > 100) {

			long diffTime = currentTime - lastShakeTime;

			lastShakeTime = currentTime;

			float x = pAccelerometerData.getX();
			float y = pAccelerometerData.getY();
			float z = pAccelerometerData.getZ();

			float speed = Math.abs(x + y + z - lastShakeX - lastShakeY
					- lastShakeZ)
					/ diffTime * 10000;

			if (speed > 1000) // set this lower or higher to change the
								// sensitivity
			{
				shaking = true;
				GameActivity.elapsedTime = System.currentTimeMillis();

				Iterator<Die> i = dice.values().iterator();

				while (i.hasNext()) {
					Die d = i.next();
					if (!d.isKept())
						jumpFace(d.GetFace());
				}
			} else if (shaking) // we were shaking, now stopped or slowed, start
								// counting and change state
			{
				shaking = false;
				SetGameState(GAME_STATE.ROLLING);

				diceSelectedThisTurn = 0;
			}

			lastShakeX = x;
			lastShakeY = y;
			lastShakeZ = z;
		}
	}

	// ===========================================================
	// Methods
	// ===========================================================

	private void addHelpButton(final float pX, final float pY) {
		final Scene scene = this.mEngine.getScene();

		button = new Sprite(pX, pY, mHelpButtonRegion) {
			@Override
			public boolean onAreaTouched(final TouchEvent pSceneTouchEvent,
					final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
				if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_DOWN) {
					DisplayToast(HELP_STRINGS[current_state.ordinal()]);
					return true;
				}

				return false;
			}
		};

		scene.getTopLayer().addEntity(button);
		scene.registerTouchArea(button);
	}

	private void addRulesButton(final float pX, final float pY) {
		final Scene scene = this.mEngine.getScene();

		button = new Sprite(pX, pY, mRulesButtonRegion) {
			@Override
			public boolean onAreaTouched(final TouchEvent pSceneTouchEvent,
					final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
				if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_DOWN) {
					mMainScene.setChildScene(mHelpScene, false, false, true);
					// mEngine.stop();
					return true;
				}

				return false;
			}
		};

		scene.getTopLayer().addEntity(button);
		scene.registerTouchArea(button);
	}

	private void addFace(final float pX, final float pY) {
		final Scene scene = this.mEngine.getScene();

		final AnimatedSprite face;
		final Body body;

		final FixtureDef objectFixtureDef = PhysicsFactory.createFixtureDef(1,
				0.5f, 0.5f);

		face = new AnimatedSprite(pX, pY,
				this.mDiceTextureRegion[this.mFaceCount]);
		body = PhysicsFactory.createBoxBody(this.mPhysicsWorld, face,
				BodyType.DynamicBody, objectFixtureDef);
		body.setLinearDamping(2.0f);
		body.setAngularDamping(2.0f);

		this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(face,
				body, true, true, false, false) {
			@Override
			public void onUpdate(float pSecondsElapsed) {
				super.onUpdate(pSecondsElapsed);

				// if (!body.isAwake()) // physics simulation in progrss? aka
				// dice in motion
				{
					// are we rolling the dice?
					if (current_state == GAME_STATE.ROLLING) {
						if ((System.currentTimeMillis() - GameActivity.elapsedTime) > 2000) {
							SetGameState(GAME_STATE.ROLLED);

							Iterator<Die> i = dice.values().iterator();

							while (i.hasNext()) {
								Die d = i.next();
								d.Roll();
							}
						}
					}
				}
			}
		});

		this.mFaceCount++;

		face.setUpdatePhysics(false);

		scene.registerTouchArea(face);
		scene.getTopLayer().addEntity(face);

		dice.put(face.hashCode(),
				new Die(body, face, DIE_INDEX[mFaceCount - 1]));
	}

	private void jumpFace(final AnimatedSprite face) {
		final Body faceBody = this.mPhysicsWorld.getPhysicsConnectorManager()
				.findBodyByShape(face);

		faceBody.setLinearVelocity(this.mTempVector.set(
				MathUtils.random(-50.0f, 50.0f),
				MathUtils.random(-50.0f, 50.0f)));
		face.animate(MathUtils.random(250, 300), true);
	}

	public void SetGameState(GAME_STATE state) {
		current_state = state;
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================
	enum GAME_STATE {
		ROLLING, NOTROLLING, ROLLED, EVALUATING_ROLL, CANROLL, GAMEOVER
	};

	public static final String[] HELP_STRINGS = {
			"Touch one or more dice in the field to keep them.\n\n"
					+ "Or touch a kept die to return it to the field if you selected wrong.", // ROLLING

			"Shake your phone to roll the dice", // NOTROLLING

			"Touch one or more dice in the field to keep them.\n\n"
					+ "Or touch a kept die to return it to the field if you selected wrong.", // ROLLED

			"", // Evaluating Roll

			"Shake your phone to roll the dice, or select another die to keep.", // CANROLL

			"" };

	@Override
	public void endContact(Contact contact) {
		if (current_state != GAME_STATE.ROLLING)
			return;

		if (contact.getFixtureA().getBody().getType() == BodyType.StaticBody) // wall
		{
			GameActivity.this.mDiceSoundSoft.play();
		} else // dice
		{
			GameActivity.this.mDiceSoundHard.play();
		}
	}

	@Override
	public void beginContact(Contact contact) {

	}

	public static void Log(Object msg) {
		Log.d("1_4__24", msg + "");
	}

	private String GetScoreText(int score) {
		return String.format("Score Last Game: %d", score);
	}

	public String statusText;

	private void DisplayToast(String message) {
		if (message == "")
			return;

		statusText = message;

		runOnUiThread(new Runnable() {
			public void run() {
				Toast.makeText(GameActivity.this, statusText, Toast.LENGTH_LONG)
						.show();
			}
		});
	}
}
